/***************************************************************************\
 * Copyright (C) by Keio University
 * VAO.cpp created in 01 2012.
 * Mail : fdesorbi@hvrl.ics.keio.ac.jp
 *
 * VAO.cpp is part of the HVRL Engine Library.
 *
 * The HVRL Engine Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * The HVRL Engine Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 \***************************************************************************/

#include "hvrl/opengl/VAO.hpp"

namespace hvrl {

VAO::VAO(void) {
	this->vertexarrayid = 0;

	this->verticesid = 0;
	this->normalsid = 0;
	this->texturesid = 0;
	this->colorsid = 0;
	this->indicesid = 0;

	this->verticesattribid = -1;
	this->normalsattribid = -1;
	this->texturesattribid = -1;
	this->colorsattribid = -1;

	this->indices = 0;
	this->initialized = false;

	this->hasshader = false;

#if CUDA_AVAILABLE == 1
	this->ressource_v = 0;
	this->ressource_c = 0;
	this->ressource_n = 0;

#endif
}

void VAO::destroy(void) {
	if (this->verticesid > 0 && glIsBuffer(this->verticesid)) {
		glDeleteBuffers(1, &this->verticesid);
	}
	if (this->normalsid > 0 && glIsBuffer(this->normalsid)) {
		glDeleteBuffers(1, &this->normalsid);
	}
	if (this->texturesid > 0 && glIsBuffer(this->texturesid)) {
		glDeleteBuffers(1, &this->texturesid);
	}
	if (this->colorsid > 0 && glIsBuffer(this->colorsid)) {
		glDeleteBuffers(1, &this->colorsid);
	}
	if (this->vertexarrayid > 0 && glIsBuffer(this->vertexarrayid)) {
		glDeleteVertexArrays(1, &this->vertexarrayid);
	}
	if (this->indicesid > 0 && glIsBuffer(this->indicesid)) {
		glDeleteBuffers(1, &this->indicesid);
	}

	this->vertexarrayid = 0;

	this->verticesid = 0;
	this->normalsid = 0;
	this->texturesid = 0;
	this->colorsid = 0;
	this->indicesid = 0;

	this->verticesattribid = -1;
	this->normalsattribid = -1;
	this->texturesattribid = -1;
	this->colorsattribid = -1;

	this->indices = 0;
	this->initialized = false;

#if CUDA_AVAILABLE == 1
	this->ressource_v = 0;
	this->ressource_c = 0;
	this->ressource_n = 0;

#endif

}

VAO::~VAO(void) {
	destroy();
}

bool VAO::init(const float* vertexbuffer, const unsigned int& sizevertices,
		const unsigned int* indicebuffer, const unsigned int& sizeindices,
		const float* normalbuffer, const float* texturebuffer,
		const float* colorbuffer, const GLenum& memorystorage) {

	if (vertexbuffer == 0 || indicebuffer == 0 || sizevertices == 0
			|| sizeindices == 0) {
		Log::add().error("VAO::init", "Input parameters are missing");
		return false;
	}

	this->destroy();

	glGenVertexArrays(1, &this->vertexarrayid);
	if (this->vertexarrayid == 0) {
		Log::add().error("VAO::init",
				"Impossible to generate a new vertex array id");
		return false;
	}

	glGenBuffers(1, &this->verticesid);
	if (this->verticesid == 0) {
		Log::add().error("VAO::init",
				"Impossible to generate a vertex buffer id");
		return false;
	}

	glGenBuffers(1, &this->indicesid);
	if (this->indicesid == 0) {
		Log::add().error("VAO::init",
				"Impossible to generate an indices buffer id");
		return false;
	}

	if (normalbuffer) {
		glGenBuffers(1, &this->normalsid);
		if (this->normalsid == 0) {
			Log::add().error("VAO::init",
					"Impossible to generate a normal buffer id");
			return false;
		}

	}

	if (texturebuffer) {
		glGenBuffers(1, &this->texturesid);
		if (this->texturesid == 0) {
			Log::add().error("VAO::init",
					"Impossible to generate a texture buffer id");
			return false;
		}
	}

	if (colorbuffer) {
		glGenBuffers(1, &this->colorsid);
		if (this->colorsid == 0) {
			Log::add().error("VAO::init",
					"Impossible to generate a color buffer id");
			return false;
		}
	}

	glBindVertexArray(this->vertexarrayid);

	glBindBuffer(GL_ARRAY_BUFFER, this->verticesid);
	glBufferData(GL_ARRAY_BUFFER, sizevertices * 4 * sizeof(GLfloat),
			vertexbuffer, memorystorage);

#if CUDA_AVAILABLE == 1
	if (hvrl::cuda::checkStatus(
			cudaGraphicsGLRegisterBuffer(&(this->ressource_v), this->verticesid,
					cudaGraphicsMapFlagsWriteDiscard)) == false) {

		hvrl::Log::add().error("VAO::init",
				"Unable to register the vertices buffer with CUDA");
		return false;
	}
#endif

	if (this->normalsid) {
		glBindBuffer(GL_ARRAY_BUFFER, this->normalsid);
		glBufferData(GL_ARRAY_BUFFER, sizevertices * 3 * sizeof(GLfloat),
				normalbuffer, memorystorage);

#if CUDA_AVAILABLE == 1
		if (hvrl::cuda::checkStatus(
				cudaGraphicsGLRegisterBuffer(&(this->ressource_n),
						this->normalsid, cudaGraphicsMapFlagsWriteDiscard))
				== false) {

			hvrl::Log::add().error("VAO::init",
					"Unable to register the normals buffer with CUDA");
			return false;
		}
#endif
	}

	if (this->texturesid) {
		glBindBuffer(GL_ARRAY_BUFFER, this->texturesid);
		glBufferData(GL_ARRAY_BUFFER, sizevertices * 2 * sizeof(GLfloat),
				texturebuffer, memorystorage);
	}

	if (this->colorsid) {
		glBindBuffer(GL_ARRAY_BUFFER, this->colorsid);
		glBufferData(GL_ARRAY_BUFFER, sizevertices * 4 * sizeof(GLfloat),
				colorbuffer, memorystorage);

#if CUDA_AVAILABLE == 1
		if (hvrl::cuda::checkStatus(
				cudaGraphicsGLRegisterBuffer(&(this->ressource_c),
						this->colorsid, cudaGraphicsMapFlagsWriteDiscard))
				== false) {

			hvrl::Log::add().error("VAO::init",
					"Unable to register the normals buffer with CUDA");
			return false;
		}
#endif
	}

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->indicesid);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeindices * sizeof(GLuint),
			indicebuffer, memorystorage);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	glBindVertexArray(0);

	this->indices = sizeindices;

	this->initialized = true;
	return true;
}

bool VAO::setProgramObject(const GLuint& po) {
	if (this->initialized == false || this->vertexarrayid == 0
			|| this->verticesid == 0) {
		Log::add().error("VAO::setProgramObject", "The VAO does not exist yet");
		return false;
	}

	glBindVertexArray(this->vertexarrayid);

	glBindBuffer(GL_ARRAY_BUFFER, this->verticesid);

	this->verticesattribid = glGetAttribLocation(po, "vertexPositionIn");
	if (this->verticesattribid == -1) {
		Log::add().error("VAO::setProgramObject",
				"Location of vertexPositionIn is incorrect");
		return false;
	}
	glVertexAttribPointer(this->verticesattribid, 4, GL_FLOAT, GL_FALSE, 0, 0);

	if (this->normalsid > 0) {

		glBindBuffer(GL_ARRAY_BUFFER, this->normalsid);
		this->normalsattribid = glGetAttribLocation(po, "vertexNormalIn");
		if (this->normalsattribid == -1) {
			Log::add().error("VAO::setProgramObject",
					"Location of vertexNormalIn is incorrect");
			return false;
		}
		glVertexAttribPointer(this->normalsattribid, 3, GL_FLOAT, GL_FALSE, 0,
				0);
	}

	if (this->colorsid > 0) {
		glBindBuffer(GL_ARRAY_BUFFER, this->colorsid);
		this->colorsattribid = glGetAttribLocation(po, "vertexColorIn");
		if (this->colorsattribid == -1) {
			Log::add().error("VAO::setProgramObject",
					"Location of vertexColorIn is incorrect");
			return false;
		}
		glVertexAttribPointer(this->colorsattribid, 4, GL_FLOAT, GL_FALSE, 0,
				0);
	}

	if (this->texturesid > 0) {
		glBindBuffer(GL_ARRAY_BUFFER, this->texturesid);
		this->texturesattribid = glGetAttribLocation(po, "vertexTextureIn");
		if (this->texturesattribid == -1) {
			Log::add().error("VAO::setProgramObject",
					"Location of vertexTextureIn is incorrect");
			return false;
		}
		glVertexAttribPointer(this->texturesattribid, 2, GL_FLOAT, GL_FALSE, 0,
				0);
	}

	glBindVertexArray(0);
	this->hasshader = true;
	return true;

}

bool VAO::updateVertices(const float* vertexbuffer,
		const unsigned int& sizevertices, const GLenum& memorystorage) {
	if (this->initialized == false || this->vertexarrayid == 0
			|| this->vertexarrayid == 0) {
		Log::add().error("VAO::updateVertices", "The VAO does not exist yet");
		return false;
	}

	glBindVertexArray(this->vertexarrayid);

	glBindBuffer(GL_ARRAY_BUFFER, this->verticesid);
	glBufferData(GL_ARRAY_BUFFER, sizevertices * 4 * sizeof(GLfloat),
			vertexbuffer, memorystorage);

	glBindVertexArray(0);

#if CUDA_AVAILABLE == 1
	if (hvrl::cuda::checkStatus(
			cudaGraphicsGLRegisterBuffer(&(this->ressource_v), this->verticesid,
					cudaGraphicsMapFlagsWriteDiscard)) == false) {

		hvrl::Log::add().error("VAO::init",
				"Unable to register the vertices buffer with CUDA");
		return false;
	}
#endif

	return true;

}

bool VAO::updateIndices(const GLuint* indicebuffer,
		const unsigned int& sizeindices, const GLenum& memorystorage) {
	if (this->initialized == false || this->vertexarrayid == 0
			|| this->indicesid == 0) {
		Log::add().error("VAO::updateIndices", "The VAO does not exist yet");
		return false;
	}

	glBindVertexArray(this->vertexarrayid);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->indicesid);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeindices * sizeof(GLuint),
			indicebuffer, memorystorage);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	this->indices = sizeindices;
	glBindVertexArray(0);
	return true;
}

bool VAO::updateNormals(const float* normalbuffer,
		const unsigned int& sizenormals, const GLenum& memorystorage) {
	if (this->initialized == false || this->vertexarrayid == 0
			|| this->normalsid == 0) {
		Log::add().error("VAO::updateNormals", "The VAO does not exist yet");
		return false;
	}

	glBindVertexArray(this->vertexarrayid);

	glBindBuffer(GL_ARRAY_BUFFER, this->normalsid);
	glBufferData(GL_ARRAY_BUFFER, sizenormals * 3 * sizeof(GLfloat),
			normalbuffer, memorystorage);

	glBindVertexArray(0);

#if CUDA_AVAILABLE == 1
	if (hvrl::cuda::checkStatus(
			cudaGraphicsGLRegisterBuffer(&(this->ressource_n), this->normalsid,
					cudaGraphicsMapFlagsWriteDiscard)) == false) {

		hvrl::Log::add().error("VAO::init",
				"Unable to register the vertices buffer with CUDA");
		return false;
	}
#endif

	return true;
}

bool VAO::updateTextures(const float* texturebuffer,
		const unsigned int& sizetextures, const GLenum& memorystorage) {
	if (this->initialized == false || this->vertexarrayid == 0
			|| this->texturesid == 0) {
		Log::add().error("VAO::updateTextures", "The VAO does not exist yet");
		return false;
	}

	glBindVertexArray(this->vertexarrayid);

	glBindBuffer(GL_ARRAY_BUFFER, this->texturesid);
	glBufferData(GL_ARRAY_BUFFER, sizetextures * 2 * sizeof(GLfloat),
			texturebuffer, memorystorage);

	glBindVertexArray(0);
	return true;

}
bool VAO::updateColors(const float* colorbuffer, const unsigned int& sizecolor,
		const GLenum& memorystorage) {
	if (this->initialized == false || this->vertexarrayid == 0
			|| this->colorsid == 0) {
		Log::add().error("VAO::updateTextures", "The VAO does not exist yet");
		return false;
	}

	glBindVertexArray(this->vertexarrayid);

	glBindBuffer(GL_ARRAY_BUFFER, this->colorsid);
	glBufferData(GL_ARRAY_BUFFER, sizecolor * 4 * sizeof(GLfloat), colorbuffer,
			GL_DYNAMIC_DRAW);

	glBindVertexArray(0);

#if CUDA_AVAILABLE == 1
	if (hvrl::cuda::checkStatus(
			cudaGraphicsGLRegisterBuffer(&(this->ressource_c), this->colorsid,
					cudaGraphicsMapFlagsWriteDiscard)) == false) {

		hvrl::Log::add().error("VAO::init",
				"Unable to register the vertices buffer with CUDA");
		return false;
	}
#endif

	return true;

}

void VAO::draw(const GLenum& aspecttype) const {
	if (this->vertexarrayid != 0) {
		glBindVertexArray(this->vertexarrayid);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->indicesid);
		if (this->verticesattribid >= 0) {
			glEnableVertexAttribArray(this->verticesattribid);
		}
		if (this->normalsattribid >= 0) {
			glEnableVertexAttribArray(this->normalsattribid);
		}
		if (this->texturesattribid >= 0) {
			glEnableVertexAttribArray(this->texturesattribid);
		}
		if (this->colorsattribid >= 0) {
			glEnableVertexAttribArray(this->colorsattribid);
		}
		glDrawElements(aspecttype, this->indices, GL_UNSIGNED_INT, 0);
		if (this->verticesattribid >= 0) {
			glDisableVertexAttribArray(this->verticesattribid);
		}
		if (this->normalsattribid >= 0) {
			glDisableVertexAttribArray(this->normalsattribid);
		}
		if (this->texturesattribid >= 0) {
			glDisableVertexAttribArray(this->texturesattribid);
		}
		if (this->colorsattribid >= 0) {
			glDisableVertexAttribArray(this->colorsattribid);
		}
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	}
}

void VAO::getVerticesArray(GLvoid ** pointer) {
	glGetVertexAttribPointerv(this->verticesattribid,
			GL_VERTEX_ATTRIB_ARRAY_POINTER, pointer);
}

void VAO::getNormalsArray(GLvoid ** pointer) {
	glGetVertexAttribPointerv(this->normalsattribid,
			GL_VERTEX_ATTRIB_ARRAY_POINTER, pointer);

}

void VAO::getTexturesArray(GLvoid ** pointer) {
	glGetVertexAttribPointerv(this->texturesattribid,
			GL_VERTEX_ATTRIB_ARRAY_POINTER, pointer);

}

void VAO::getColorsArray(GLvoid ** pointer) {
	glGetVertexAttribPointerv(this->colorsattribid,
			GL_VERTEX_ATTRIB_ARRAY_POINTER, pointer);

}

}

